/**
 * Copyright 2006 gworks.com.au
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License. (http://www.apache.org/licenses/LICENSE-2.0)
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 
 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for 
 * the specific language governing permissions and limitations under the License.
 *
 * <dhamma-message src="Atisha (11th century Tibetan Buddhist master)">
 * 	The greatest achievement is selflessness. The greatest worth is self-mastery.
 * 	The greatest quality is seeking to serve others. The greatest precept is continual awareness.
 * 	The greatest medicine is the emptiness of everything. The greatest action is not conforming with the worlds ways.
 * 	The greatest magic is transmuting the passions. The greatest generosity is non-attachment.
 * 	The greatest goodness is a peaceful mind. The greatest patience is humility.
 * 	The greatest effort is not concerned with results. The greatest meditation is a mind that lets go.
 * 	The greatest wisdom is seeing through appearances. 
 * </dhamma-message>
 */
package au.com.gworks.jump.app.util;

import java.security.Principal;
import java.text.ParseException;
import java.util.Date;
import java.util.StringTokenizer;
import java.util.TimeZone;

import javax.servlet.FilterConfig;
import javax.servlet.http.HttpServletRequest;

import org.javaongems.runtime.io.PathUtils;
import org.javaongems.runtime.lang.StringUtils;


import au.com.gworks.jump.io.PathStatus;
import au.com.gworks.jump.io.ResourceAttributes;
import au.com.gworks.jump.io.ResourceNotFound;
import au.com.gworks.jump.io.UnauthorisedAccess;
import au.com.gworks.jump.util.TimeUtils;

/**
 * Rationale:
 * <p>
 * To provide pluggable repository preparation and closer wrapped around the request
 * </p>
 * Responsibilities:
 * <ul>
 * 	<li> open a repository for the given namespace and user to be used for the duration of the thread/request
 * 	<li> create a RequestPathInfo object
 * 	<li> close the repository after upon completion of the thread/request 
 * </ul>
 * Collaborators:
 * <ul>
 * 	<li> called by the RequestInterceptor
 * 	<li> user definable repository provider
 * </ul>
 * Constraints:
 * <ul>
 * 	<li> only a single instance will be created and associated to the RequestInterceptor
 * 	<li> implementations must be thread safe and should consider avoiding instance
 * 		 variables
 * </ul>
 * 
 * @see RequestInterceptor
 * 
 * @author Ashin Wimalajeewa (ash)
 */
public interface RequestInterceptorPlugin {
	public String getRepositoryNamespace();
	public void init(FilterConfig filterConfig);
	public void openRepository(String namespace, Principal requester);
	public RequestPathInfo createRequestPathInfo(HttpServletRequest rqst) throws UnauthorisedAccess, IllegalArgumentException;
	public void closeRepository();
	
	static public abstract class AbstractBase implements RequestInterceptorPlugin {
		public abstract Integer getLatestRevision();
		public abstract int	getPathStatus(String path, Integer revision);
		public abstract ResourceAttributes getResourceAttributes(String path, Integer revision) throws UnauthorisedAccess, ResourceNotFound;
		public abstract Integer getRevisionForDate(Date date);
		public abstract boolean isAdministator();
		
		public RequestPathInfo createRequestPathInfo(HttpServletRequest req) throws UnauthorisedAccess, IllegalArgumentException {
			String path = req.getPathInfo();
			boolean resetPath = (path == null);
			if (resetPath) 
				path = PathUtils.FORWARD_SLASH;
			String namespace = getRepositoryNamespace();
			String uri = req.getRequestURI() + (resetPath ? PathUtils.FORWARD_SLASH: "");
			uri = StringUtils.decodeUrl(uri);
			String mainCtx = EnvironmentUtils.getApplicationContext();
			int mainCtxLen = mainCtx.length();
			int pathInfoPos = uri.indexOf(path, mainCtxLen+1);
			String mainPlusSubCtx = uri.substring(0, pathInfoPos);
			String subCtx = mainPlusSubCtx.substring(mainCtxLen+1);
			
			String[] revId = new String[1];
			String namespaceCtx = getDocRevisionSubcontext(path, 0, revId);
			path = path.substring(namespaceCtx.length());
			if (StringUtils.isEmpty(path))
				path = PathUtils.FORWARD_SLASH;

			Object[] revDetails = parseRevisionDetails(revId[0], false);
			Integer headRevision = (Integer) revDetails[3];
			Integer revision = (Integer) revDetails[0];
			int status = getPathStatus(path, revision);
			ResourceAttributes ra = null;
			if (status > PathStatus.DOESNT_EXIST) {
				try {
					ra = getResourceAttributes(path, revision);
				} catch (Exception err) {
					err.printStackTrace();
					// ignore
				}
			}
			boolean wasRevSpecified = ((Boolean)revDetails[4]).booleanValue();
			boolean isAdmin = isAdministator();
			return new RequestPathInfo(revId[0], subCtx, namespace, isAdmin, path, wasRevSpecified, revision, headRevision, ra, status);
		}
		
		protected String getDocRevisionSubcontext(String uri, int skipCtxs, String[] revIdStore) {
			skipCtxs++;	// need to cater for namespace
			StringBuffer ret = new StringBuffer(PathUtils.FORWARD_SLASH);
			if (uri == null)
				uri = PathUtils.FORWARD_SLASH;
			StringTokenizer tok = new StringTokenizer(uri, PathUtils.FORWARD_SLASH);
			String dir = null;
			for (int i = 0; i < skipCtxs; i++) {
				if (tok.hasMoreTokens())
					dir = tok.nextToken();
			}
			if (dir != null)
				ret.append(dir);
			if (tok.hasMoreTokens()) {
				String subCtx = tok.nextToken();
				if (subCtx.startsWith(EnvironmentUtils.ENV_REV_SPECIFIER_BEGIN) && subCtx.endsWith(EnvironmentUtils.ENV_REV_SPECIFIER_END)) {
					String revId = subCtx.substring(1, subCtx.length() - 1);
					if (revIdStore != null)
						revIdStore[0] = revId;
					if (dir != null)
						ret.append(PathUtils.FORWARD_SLASH);
					ret.append(subCtx);
				}
			}
			return ret.toString();
		}
		
		/**
		 * @param revId	- a revision specification
		 * @return	[0] - revision request
		 * 			[1] - is head revision
		 * 			[2] - date/time string associated with [1] if any, otherwise now
		 * 			[3] - head revision
		 * 			[4] - was revision specified
		 */
		protected Object[] parseRevisionDetails(String revId, boolean suppressAuthExcp) 
				throws UnauthorisedAccess, IllegalArgumentException {
			Object[] ret = new Object[6];
			ret[3] = getLatestRevision();
			boolean useDefaults = true;
			if (revId != null) {
				char id = revId.charAt(0);
				revId = revId.substring(1);
				try {
					switch (id) {
					case EnvironmentUtils.ENV_REV_SPECIFIER_DATE:
						Date revDate = null;
						if (revId.length() > 10)
							revDate = EnvironmentUtils.REV_EXT_DATE_FORMATTER.parse(revId);
						else
							revDate = EnvironmentUtils.REV_DATE_FORMATTER.parse(revId);
						ret[0] = getRevisionForDate(revDate);
						break;
					case EnvironmentUtils.ENV_REV_SPECIFIER_REVISION:
						ret[0] = new Integer(revId);
						break;
					default:
						throw new IllegalArgumentException("unsupported revision token");
					}
					ret[4] = Boolean.TRUE;
					ResourceAttributes ra = getResourceAttributes(PathUtils.FORWARD_SLASH, (Integer) ret[0]);
					long lastMod = TimeUtils.toZonedTime(ra.getLastModified(), TimeZone.getDefault());
					ret[2] = EnvironmentUtils.REV_DATE_PRETTY_FORMATTER.format(new Date(lastMod));
					ret[5] = new Long(lastMod);
					useDefaults = false;
				} catch (ParseException err) {
					useDefaults = true;
				} catch (ResourceNotFound err) {
					useDefaults = true;
				} catch (UnauthorisedAccess err) {
					if (suppressAuthExcp) 
						useDefaults = true;
					else 
						throw err;
				}
			}
			if (useDefaults) {
				Date revDate = new Date();
				ret[0] = ret[3];
				ret[1] = Boolean.TRUE;
				ret[2] = EnvironmentUtils.REV_DATE_PRETTY_FORMATTER.format(revDate);
				ret[4] = Boolean.FALSE;
				ret[5] = new Long(revDate.getTime());
			}
			ret[1] = new Boolean(ret[0].equals(ret[3]));
			return ret;
		}
	}
}